/*
** $Id: lcorolib.c,v 1.10 2016/04/11 19:19:55 roberto Exp $
** Coroutine Library
** See Copyright Notice in lua.h
*/

#define lcorolib_c
#define LUA_LIB

#include "lprefix.h"


#include <stdlib.h>

#include "lua.h"

#include "lauxlib.h"
#include "lualib.h"


static lua_State* getco( lua_State* L ) {
    lua_State* co = lua_tothread( L, 1 );
    luaL_argcheck( L, co, 1, "thread expected" );
    return co;
}


static int auxresume( lua_State* L, lua_State* co, int narg ) {
    int status;

    if ( !lua_checkstack( co, narg ) ) {
        lua_pushliteral( L, "too many arguments to resume" );
        return -1;  /* error flag */
    }

    if ( lua_status( co ) == LUA_OK && lua_gettop( co ) == 0 ) {
        lua_pushliteral( L, "cannot resume dead coroutine" );
        return -1;  /* error flag */
    }

    lua_xmove( L, co, narg );
    status = lua_resume( co, L, narg );

    if ( status == LUA_OK || status == LUA_YIELD ) {
        int nres = lua_gettop( co );

        if ( !lua_checkstack( L, nres + 1 ) ) {
            lua_pop( co, nres ); /* remove results anyway */
            lua_pushliteral( L, "too many results to resume" );
            return -1;  /* error flag */
        }

        lua_xmove( co, L, nres ); /* move yielded values */
        return nres;
    }
    else {
        lua_xmove( co, L, 1 ); /* move error message */
        return -1;  /* error flag */
    }
}


static int luaB_coresume( lua_State* L ) {
    lua_State* co = getco( L );
    int r;
    r = auxresume( L, co, lua_gettop( L ) - 1 );

    if ( r < 0 ) {
        lua_pushboolean( L, 0 );
        lua_insert( L, -2 );
        return 2;  /* return false + error message */
    }
    else {
        lua_pushboolean( L, 1 );
        lua_insert( L, -( r + 1 ) );
        return r + 1;  /* return true + 'resume' returns */
    }
}


static int luaB_auxwrap( lua_State* L ) {
    lua_State* co = lua_tothread( L, lua_upvalueindex( 1 ) );
    int r = auxresume( L, co, lua_gettop( L ) );

    if ( r < 0 ) {
        if ( lua_type( L, -1 ) == LUA_TSTRING ) { /* error object is a string? */
            luaL_where( L, 1 ); /* add extra info */
            lua_insert( L, -2 );
            lua_concat( L, 2 );
        }

        return lua_error( L ); /* propagate error */
    }

    return r;
}


static int luaB_cocreate( lua_State* L ) {
    lua_State* NL;
    luaL_checktype( L, 1, LUA_TFUNCTION );
    NL = lua_newthread( L );
    lua_pushvalue( L, 1 ); /* move function to top */
    lua_xmove( L, NL, 1 ); /* move function from L to NL */
    return 1;
}


static int luaB_cowrap( lua_State* L ) {
    luaB_cocreate( L );
    lua_pushcclosure( L, luaB_auxwrap, 1 );
    return 1;
}


static int luaB_yield( lua_State* L ) {
    return lua_yield( L, lua_gettop( L ) );
}


static int luaB_costatus( lua_State* L ) {
    lua_State* co = getco( L );

    if ( L == co ) {
        lua_pushliteral( L, "running" );
    }
    else {
        switch ( lua_status( co ) ) {
        case LUA_YIELD:
            lua_pushliteral( L, "suspended" );
            break;

        case LUA_OK: {
            lua_Debug ar;

            if ( lua_getstack( co, 0, &ar ) > 0 ) { /* does it have frames? */
                lua_pushliteral( L, "normal" );    /* it is running */
            }
            else if ( lua_gettop( co ) == 0 ) {
                lua_pushliteral( L, "dead" );
            }
            else {
                lua_pushliteral( L, "suspended" );    /* initial state */
            }

            break;
        }

        default:  /* some error occurred */
            lua_pushliteral( L, "dead" );
            break;
        }
    }

    return 1;
}


static int luaB_yieldable( lua_State* L ) {
    lua_pushboolean( L, lua_isyieldable( L ) );
    return 1;
}


static int luaB_corunning( lua_State* L ) {
    int ismain = lua_pushthread( L );
    lua_pushboolean( L, ismain );
    return 2;
}


static const luaL_Reg co_funcs[] = {
    {"create", luaB_cocreate},
    {"resume", luaB_coresume},
    {"running", luaB_corunning},
    {"status", luaB_costatus},
    {"wrap", luaB_cowrap},
    {"yield", luaB_yield},
    {"isyieldable", luaB_yieldable},
    {NULL, NULL}
};



LUAMOD_API int luaopen_coroutine( lua_State* L ) {
    luaL_newlib( L, co_funcs );
    return 1;
}

